<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>可切换重力方向的投掷动画</title>
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }

      body {
        font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        min-height: 100vh;
        background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d);
        color: white;
        padding: 20px;
      }

      .container {
        width: 100%;
        max-width: 900px;
        text-align: center;
      }

      h1 {
        margin-bottom: 15px;
        font-size: 2.5rem;
        text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
      }

      .description {
        margin-bottom: 25px;
        font-size: 1.1rem;
        line-height: 1.6;
        max-width: 700px;
        margin-left: auto;
        margin-right: auto;
      }

      .scene {
        position: relative;
        width: 100%;
        height: 450px;
        background-color: rgba(255, 255, 255, 0.1);
        border-radius: 12px;
        margin-bottom: 20px;
        overflow: hidden;
        box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
        border: 1px solid rgba(255, 255, 255, 0.2);
      }

      .thrower {
        position: absolute;
        bottom: 50px;
        left: 100px;
        width: 80px;
        height: 150px;
        z-index: 5;
      }

      .thrower-body {
        position: absolute;
        width: 60px;
        height: 100px;
        background: linear-gradient(to bottom, #8b4513, #a0522d);
        border-radius: 30px 30px 0 0;
        bottom: 0;
        left: 10px;
      }

      .thrower-head {
        position: absolute;
        width: 50px;
        height: 50px;
        background: linear-gradient(45deg, #ffdab9, #f4a460);
        border-radius: 50%;
        bottom: 100px;
        left: 15px;
      }

      .thrower-arm {
        position: absolute;
        width: 15px;
        height: 70px;
        background: linear-gradient(to bottom, #8b4513, #a0522d);
        border-radius: 10px;
        bottom: 60px;
        left: 70px;
        transform-origin: bottom center;
        transition: transform 0.3s ease-out;
      }

      .object-container {
        position: absolute;
        left: 120px;
        bottom: 130px;
        transform: translate3d(0, 0, 0);
        will-change: transform;
        z-index: 10;
      }

      .object {
        width: 40px;
        height: 40px;
        background: linear-gradient(45deg, #ff416c, #ff4b2b);
        border-radius: 50%;
        cursor: grab;
        display: flex;
        align-items: center;
        justify-content: center;
        color: white;
        font-weight: bold;
        user-select: none;
        box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
        transition: transform 0.2s;
        transform: translate3d(0, 0, 0);
        will-change: transform;
      }

      .object:active {
        cursor: grabbing;
        transform: scale(1.1);
      }

      .controls {
        display: flex;
        gap: 15px;
        margin-bottom: 20px;
        flex-wrap: wrap;
        justify-content: center;
      }

      button {
        padding: 10px 20px;
        background: rgba(255, 255, 255, 0.2);
        border: none;
        border-radius: 50px;
        color: white;
        font-size: 1rem;
        cursor: pointer;
        transition: all 0.3s;
        backdrop-filter: blur(5px);
        border: 1px solid rgba(255, 255, 255, 0.3);
      }

      button:hover {
        background: rgba(255, 255, 255, 0.3);
        transform: translateY(-2px);
      }

      .stats {
        display: flex;
        justify-content: center;
        gap: 30px;
        margin-top: 20px;
        flex-wrap: wrap;
      }

      .stat-item {
        background: rgba(255, 255, 255, 0.1);
        padding: 15px 25px;
        border-radius: 10px;
        min-width: 150px;
        backdrop-filter: blur(5px);
        border: 1px solid rgba(255, 255, 255, 0.2);
      }

      .stat-value {
        font-size: 1.5rem;
        font-weight: bold;
        margin-top: 5px;
        color: #ffd700;
      }

      .instructions {
        margin-top: 20px;
        font-style: italic;
        opacity: 0.8;
        max-width: 600px;
      }

      .trail {
        position: absolute;
        width: 6px;
        height: 6px;
        background-color: rgba(255, 215, 0, 0.7);
        border-radius: 50%;
        pointer-events: none;
        z-index: 2;
        transform: translate3d(0, 0, 0);
        will-change: transform;
      }

      .ground {
        position: absolute;
        bottom: 0;
        left: 0;
        width: 100%;
        height: 50px;
        background: linear-gradient(to top, #8b4513, #a0522d);
        z-index: 1;
      }

      .ceiling {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 50px;
        background: linear-gradient(to bottom, #8b4513, #a0522d);
        z-index: 1;
        display: none;
      }

      .target {
        position: absolute;
        bottom: 50px;
        right: 150px;
        width: 80px;
        height: 80px;
        background: radial-gradient(circle, #ff0000, #8b0000);
        border-radius: 50%;
        display: flex;
        align-items: center;
        justify-content: center;
        color: white;
        font-weight: bold;
        box-shadow: 0 0 20px rgba(255, 0, 0, 0.5);
        z-index: 3;
        transform: translate3d(0, 0, 0);
        will-change: transform;
      }

      .target::before,
      .target::after {
        content: "";
        position: absolute;
        width: 60px;
        height: 4px;
        background: white;
        border-radius: 2px;
      }

      .target::before {
        transform: rotate(45deg);
      }

      .target::after {
        transform: rotate(-45deg);
      }

      .gravity-indicator {
        position: absolute;
        top: 10px;
        right: 10px;
        background: rgba(0, 0, 0, 0.5);
        padding: 5px 10px;
        border-radius: 20px;
        font-size: 0.9rem;
        z-index: 10;
      }

      .performance-info {
        margin-top: 15px;
        font-size: 0.9rem;
        opacity: 0.7;
      }

      @media (max-width: 600px) {
        h1 {
          font-size: 1.8rem;
        }

        .scene {
          height: 400px;
        }

        .stats {
          flex-direction: column;
          align-items: center;
        }

        .thrower {
          left: 50px;
        }

        .target {
          right: 80px;
        }

        .object-container {
          left: 80px;
        }
      }
    </style>
  </head>
  <body>
    <div class="container">
      <h1>可切换重力方向的投掷动画</h1>
      <p class="description">
        使用CSS3
        transform和requestAnimationFrame实现的流畅投掷动画，支持重力方向切换。
      </p>

      <div class="controls">
        <button id="resetBtn">重置投掷物</button>
        <button id="toggleTrailBtn">切换轨迹显示</button>
        <button id="slowMotionBtn">切换慢动作</button>
        <button id="gravityDirectionBtn">切换重力方向（向下）</button>
        <button id="autoThrowBtn">自动投掷演示</button>
      </div>

      <div class="scene" id="scene">
        <div class="gravity-indicator">重力方向：向下 ↓</div>
        <div class="ground"></div>
        <div class="ceiling" id="ceiling"></div>
        <div class="thrower">
          <div class="thrower-body"></div>
          <div class="thrower-head"></div>
          <div class="thrower-arm" id="throwerArm"></div>
        </div>
        <div class="object-container" id="objectContainer">
          <div class="object" id="object">投掷物</div>
        </div>
        <div class="target">靶心</div>
      </div>

      <div class="stats">
        <div class="stat-item">
          <div>水平速度</div>
          <div class="stat-value" id="velocityX">0 m/s</div>
        </div>
        <div class="stat-item">
          <div>垂直速度</div>
          <div class="stat-value" id="velocityY">0 m/s</div>
        </div>
        <div class="stat-item">
          <div>投掷次数</div>
          <div class="stat-value" id="throwCount">0</div>
        </div>
        <div class="stat-item">
          <div>命中次数</div>
          <div class="stat-value" id="hitCount">0</div>
        </div>
      </div>

      <p class="instructions">
        提示：拖动投掷物并释放，观察抛物线运动轨迹。可以切换重力方向，体验向上或向下掉落的效果。
      </p>
      <p class="performance-info">
        使用CSS3 transform和requestAnimationFrame实现，性能更优
      </p>
    </div>

    <script>
      document.addEventListener("DOMContentLoaded", () => {
        const object = document.getElementById("object");
        const objectContainer = document.getElementById("objectContainer");
        const scene = document.getElementById("scene");
        const throwerArm = document.getElementById("throwerArm");
        const resetBtn = document.getElementById("resetBtn");
        const toggleTrailBtn = document.getElementById("toggleTrailBtn");
        const slowMotionBtn = document.getElementById("slowMotionBtn");
        const gravityDirectionBtn = document.getElementById(
          "gravityDirectionBtn",
        );
        const autoThrowBtn = document.getElementById("autoThrowBtn");
        const velocityXEl = document.getElementById("velocityX");
        const velocityYEl = document.getElementById("velocityY");
        const throwCountEl = document.getElementById("throwCount");
        const hitCountEl = document.getElementById("hitCount");
        const target = document.querySelector(".target");
        const ceiling = document.getElementById("ceiling");
        const gravityIndicator = document.querySelector(".gravity-indicator");

        // 物理参数
        let gravity = 0.5;
        let gravityDirection = 1; // 1表示向下，-1表示向上
        const damping = 0.7;
        const airResistance = 0.99;
        let showTrail = true;
        let slowMotionEnabled = false;
        const slowMotionFactor = 3;

        // 状态变量
        let isDragging = false;
        let isThrown = false;
        let throwCount = 0;
        let hitCount = 0;
        let velocityX = 0;
        let velocityY = 0;
        let lastX = 0;
        let lastY = 0;
        let lastTime = 0;
        let animationId = null;
        const currentTransform = { x: 0, y: 0 };

        // 投掷物初始位置
        const initialPosition = {
          x: 120,
          y: scene.offsetHeight - 180,
        };

        // 设置初始位置
        updateTransform(initialPosition.x, initialPosition.y);

        // 更新transform属性
        function updateTransform(x, y) {
          currentTransform.x = x;
          currentTransform.y = y;
          objectContainer.style.transform = `translate3d(${x}px, ${-y}px, 0)`;
        }

        // 鼠标/触摸事件处理
        object.addEventListener("mousedown", startDrag);
        object.addEventListener("touchstart", startDrag);

        document.addEventListener("mousemove", drag);
        document.addEventListener("touchmove", drag);

        document.addEventListener("mouseup", endDrag);
        document.addEventListener("touchend", endDrag);

        // 按钮事件
        resetBtn.addEventListener("click", resetObject);
        toggleTrailBtn.addEventListener("click", toggleTrail);
        slowMotionBtn.addEventListener("click", toggleSlowMotion);
        gravityDirectionBtn.addEventListener("click", toggleGravityDirection);
        autoThrowBtn.addEventListener("click", autoThrow);

        // 开始拖动
        function startDrag(e) {
          e.preventDefault();
          isDragging = true;
          isThrown = false;
          object.style.cursor = "grabbing";

          if (animationId) {
            cancelAnimationFrame(animationId);
            animationId = null;
          }

          lastX = e.clientX || e.touches[0].clientX;
          lastY = e.clientY || e.touches[0].clientY;
          lastTime = Date.now();

          updateArmAngle();
        }

        // 拖动中
        function drag(e) {
          if (!isDragging) return;

          e.preventDefault();
          const clientX = e.clientX || e.touches[0].clientX;
          const clientY = e.clientY || e.touches[0].clientY;

          // 计算新位置
          const deltaX = clientX - lastX;
          const deltaY = clientY - lastY;

          let newX = currentTransform.x + deltaX;
          let newY = currentTransform.y - deltaY; // Y轴方向取反

          // 限制在场景内
          newX = Math.max(
            0,
            Math.min(newX, scene.offsetWidth - object.offsetWidth),
          );
          newY = Math.max(
            0,
            Math.min(newY, scene.offsetHeight - object.offsetHeight - 50),
          );

          updateTransform(newX, newY);

          // 计算速度
          const currentTime = Date.now();
          const deltaTime = currentTime - lastTime;

          if (deltaTime > 0) {
            velocityX = (deltaX / deltaTime) * 100;
            velocityY = (-deltaY / deltaTime) * 100; // Y轴方向取反

            updateStats();
          }

          lastX = clientX;
          lastY = clientY;
          lastTime = currentTime;

          updateArmAngle();
        }

        // 更新投掷者手臂角度
        function updateArmAngle() {
          const objectRect = objectContainer.getBoundingClientRect();
          const throwerRect = throwerArm.getBoundingClientRect();

          const dx =
            objectRect.left +
            objectRect.width / 2 -
            (throwerRect.left + throwerRect.width / 2);
          const dy =
            throwerRect.top +
            throwerRect.height / 2 -
            (objectRect.top + objectRect.height / 2);

          const angle = (Math.atan2(dy, dx) * 180) / Math.PI;
          throwerArm.style.transform = `rotate(${angle}deg)`;
        }

        // 结束拖动（投掷）
        function endDrag() {
          if (!isDragging) return;

          isDragging = false;
          isThrown = true;
          object.style.cursor = "grab";

          throwCount++;
          throwCountEl.textContent = throwCount;

          // 使用requestAnimationFrame开始动画
          animate();
        }

        // 动画循环
        function animate() {
          if (!isThrown) return;

          // 应用物理效果 - 根据重力方向调整
          velocityY -= gravity * gravityDirection;
          velocityX *= airResistance;
          velocityY *= airResistance;

          // 计算新位置
          let newX = currentTransform.x + velocityX;
          let newY = currentTransform.y - velocityY; // Y轴方向取反

          // 边界碰撞检测
          let hitBoundary = false;

          if (newX <= 0 || newX >= scene.offsetWidth - object.offsetWidth) {
            velocityX = -velocityX * damping;
            newX = newX <= 0 ? 0 : scene.offsetWidth - object.offsetWidth;
            hitBoundary = true;
          }

          // 根据重力方向调整上下边界检测
          if (gravityDirection === 1) {
            // 向下重力 - 检测地面碰撞
            if (
              newY <= 0 ||
              newY >= scene.offsetHeight - object.offsetHeight - 50
            ) {
              velocityY = -velocityY * damping;
              newY =
                newY <= 0 ? 0 : scene.offsetHeight - object.offsetHeight - 50;
              hitBoundary = true;
            }
          } else {
            // 向上重力 - 检测天花板碰撞
            if (
              newY <= 50 ||
              newY >= scene.offsetHeight - object.offsetHeight
            ) {
              velocityY = -velocityY * damping;
              newY = newY <= 50 ? 50 : scene.offsetHeight - object.offsetHeight;
              hitBoundary = true;
            }
          }

          // 停止条件
          if (Math.abs(velocityY) < 0.5 && Math.abs(velocityX) < 0.5) {
            isThrown = false;
            velocityX = 0;
            velocityY = 0;
            updateStats();
            return;
          }

          // 使用transform更新位置
          updateTransform(newX, newY);

          // 检测命中
          checkTargetHit();

          // 添加轨迹
          if (showTrail) {
            addTrailPoint(
              newX + object.offsetWidth / 2,
              scene.offsetHeight - newY - object.offsetHeight / 2,
            );
          }

          updateStats();

          // 继续动画
          animationId = requestAnimationFrame(animate);
        }

        // 检测命中
        function checkTargetHit() {
          const objectRect = objectContainer.getBoundingClientRect();
          const targetRect = target.getBoundingClientRect();
          const sceneRect = scene.getBoundingClientRect();

          const objX =
            objectRect.left - sceneRect.left + object.offsetWidth / 2;
          const objY = objectRect.top - sceneRect.top + object.offsetHeight / 2;
          const targetX =
            targetRect.left - sceneRect.left + target.offsetWidth / 2;
          const targetY =
            targetRect.top - sceneRect.top + target.offsetHeight / 2;

          const distance = Math.sqrt(
            (objX - targetX) ** 2 + (objY - targetY) ** 2,
          );

          if (distance < 40) {
            hitCount++;
            hitCountEl.textContent = hitCount;

            target.style.transform = "scale(1.2)";
            target.style.boxShadow = "0 0 30px rgba(255, 255, 0, 0.8)";

            setTimeout(() => {
              target.style.transform = "scale(1)";
              target.style.boxShadow = "0 0 20px rgba(255, 0, 0, 0.5)";
            }, 300);
          }
        }

        // 添加轨迹点
        function addTrailPoint(x, y) {
          const trail = document.createElement("div");
          trail.className = "trail";
          trail.style.transform = `translate3d(${x}px, ${y}px, 0)`;
          scene.appendChild(trail);

          setTimeout(() => {
            trail.style.opacity = "0";
            trail.style.transition = "opacity 1s";
            setTimeout(() => {
              if (trail.parentNode) {
                trail.parentNode.removeChild(trail);
              }
            }, 1000);
          }, 3000);
        }

        // 更新统计数据
        function updateStats() {
          velocityXEl.textContent = velocityX.toFixed(1) + " m/s";
          velocityYEl.textContent = velocityY.toFixed(1) + " m/s";
        }

        // 重置投掷物
        function resetObject() {
          isDragging = false;
          isThrown = false;
          velocityX = 0;
          velocityY = 0;

          if (animationId) {
            cancelAnimationFrame(animationId);
            animationId = null;
          }

          updateTransform(initialPosition.x, initialPosition.y);
          throwerArm.style.transform = "rotate(0deg)";

          const trails = document.querySelectorAll(".trail");
          trails.forEach((trail) => trail.remove());

          updateStats();
        }

        // 切换轨迹显示
        function toggleTrail() {
          showTrail = !showTrail;
          toggleTrailBtn.textContent = showTrail ? "隐藏轨迹" : "显示轨迹";

          if (!showTrail) {
            const trails = document.querySelectorAll(".trail");
            trails.forEach((trail) => trail.remove());
          }
        }

        // 切换慢动作
        function toggleSlowMotion() {
          slowMotionEnabled = !slowMotionEnabled;
          slowMotionBtn.textContent = slowMotionEnabled
            ? "关闭慢动作"
            : "开启慢动作";
          gravity = slowMotionEnabled ? 0.2 : 0.5;
        }

        // 切换重力方向
        function toggleGravityDirection() {
          gravityDirection = -gravityDirection; // 切换方向

          // 更新UI
          if (gravityDirection === 1) {
            gravityDirectionBtn.textContent = "切换重力方向（向下）";
            gravityIndicator.textContent = "重力方向：向下 ↓";
            ceiling.style.display = "none";
          } else {
            gravityDirectionBtn.textContent = "切换重力方向（向上）";
            gravityIndicator.textContent = "重力方向：向上 ↑";
            ceiling.style.display = "block";
          }

          // 重置场景
          resetObject();
        }

        // 自动投掷演示
        function autoThrow() {
          resetObject();

          const angle = Math.random() * 30 + 30;
          const speed = Math.random() * 10 + 15;

          velocityX = Math.cos((angle * Math.PI) / 180) * speed;
          velocityY = Math.sin((angle * Math.PI) / 180) * speed;

          // 根据重力方向调整初始速度
          if (gravityDirection === -1) {
            velocityY = -velocityY;
          }

          isThrown = true;
          throwCount++;
          throwCountEl.textContent = throwCount;

          animate();
        }
      });
    </script>
  </body>
</html>
